问题
设计一个类的序列化形式和设计该类的API 同样重要,因此在没有认真考虑好默认的序列化形式是否合适之前,不要贸然使用默认的序列化行为。在作出决定之前,你需要从灵活性、性能和正确性多个角度对这种编码形式进行考察。一般来讲,只有当你自行设计的自定义序列化形式与默认的形式基本相同时,才能接受默认的序列化形式。选择合适的序列化方式,有哪些注意事项?
答案
默认的序列化形式描述了该兑现内部所包含的数据,以及每一个可以从这个对象到达的其他对象的内部数据,即完整了描述了所有对象被链接起来的拓扑结构。对于一个对象来说,理想的序列化形式应该只包含该对象所表示的逻辑数据,而逻辑数据和物理表示应该是相互独立。也就是说,如果一个对象的物理表示等同于它的逻辑内容的话,就适合使用默认的序列化形式。 有这样一个例子
public class Name implements Serializable { private final String lastName; private final String firstName; private final String middleName; ... ... }
从逻辑的角度而言,Name类可以简单的由lastName、firstName以及middleName三个属性来进行表示,也就是说,这三个属性可以精确的反映出它的逻辑内容。因此,这种情况可以采用默认的序列化形式,同样也需要在readObject中进行参数有效性检测和保护性拷贝。
使用默认序列化形式,当一个或多个域字段被标记为transient 时,如果要进行反序列化,这些域字段都将被初始化为其类型默认值,如对象引用域被置为null,数值基本域的默认值为0,boolean域的默认值为false。如果这些值不能被任何transient 域所修饰,你就必须提供一个readObject方法。它首先调用defaultReadObject,然后再把这些transient 域进行恢复为之前的初始值;同样的,在序列化过程中,被transient修饰的实例域会被省略掉
在序列化过程中,虚拟机会试图调用对象类里的writeObject() 和readObject(),因此可以在readObject和writeObject方法中实现自己的序列化逻辑。就算没有实现特定的逻辑也应该调用默认的ObjectOutputStream.defaultWriteObject() 和ObjectInputStream.defaultReadObject()方法,这样就可以保证向前或者向后的兼容性;
无论你选择了哪种序列化形式,都要为自己编写的每个可序列化的类声明一个显式的序列版本UID。这样可以避免序列版本UID成为潜在的不兼容根源,同时也会带来小小的性能好处,因为不需要去算序列版本UID。
结论
当你决定将一个类设计成可序列化的时候,就应该详细考虑应该采用什么样的序列化形式。只有当默认的序列化形式能够合理的描述对象的逻辑状态时,才能使用默认的序列化形式。否则就要设计一个自定义的序列化形式,通过它合理的描述出对象的状态。